home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / hplip / fax / fax.py < prev    next >
Encoding:
Python Source  |  2009-04-14  |  29.6 KB  |  939 lines

  1. # -*- coding: utf-8 -*-
  2. #
  3. # (c) Copyright 2003-2009 Hewlett-Packard Development Company, L.P.
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  18. #
  19. # Author: Don Welch
  20. #
  21.  
  22. from __future__ import generators
  23.  
  24. # Std Lib
  25. import sys
  26. import os
  27. import threading
  28. import cPickle
  29. import time
  30. from cStringIO import StringIO
  31. import struct
  32.  
  33. # Local
  34. from base.g import *
  35. from base.codes import *
  36. from base.ldif import LDIFParser
  37. from base import device, utils, vcard
  38. from prnt import cups
  39.  
  40. try:
  41.     import coverpages
  42. except ImportError:
  43.     pass
  44.  
  45. try:
  46.     import dbus
  47. except ImportError:
  48.     log.error("dbus is required for PC send fax.")
  49.  
  50.  
  51. # Update queue values (Send thread ==> UI)
  52. STATUS_IDLE = 0
  53. STATUS_PROCESSING_FILES = 1
  54. STATUS_SENDING_TO_RECIPIENT = 2
  55. STATUS_DIALING = 3
  56. STATUS_CONNECTING = 4
  57. STATUS_SENDING = 5
  58. STATUS_COMPLETED = 6
  59. STATUS_CREATING_COVER_PAGE = 7
  60. STATUS_ERROR = 8
  61. STATUS_BUSY = 9
  62. STATUS_CLEANUP = 10
  63.  
  64. # Event queue values (UI ==> Send thread)
  65. EVENT_FAX_SEND_CANCELED = 1
  66. # Other values in queue are:
  67. #EVENT_FAX_RENDER_COMPLETE_BEGIN = 8010
  68. #EVENT_FAX_RENDER_COMPLETE_SENDDATA = 8011
  69. #EVENT_FAX_RENDER_COMPLETE_END = 8012
  70.  
  71. # **************************************************************************** #
  72. # HPLIP G3 Fax File Format (big endian)
  73. #
  74. # #==============================================#
  75. # # File Header: Total 28 bytes                  #
  76. # #..............................................#
  77. # # Magic bytes: 8 bytes ("hplip_g3")            #
  78. # # Format version: 8 bits (1)                   #
  79. # # Total pages in file(=p): 32 bits             #
  80. # # Hort DPI: 16 bits (200 or 300)               #
  81. # # Vert DPI: 16 bits (100, 200, or 300)         #
  82. # # Page Size: 8 bits (0=Unk, 1=Letter, 2=A4,    #
  83. # #                    3=Legal)                  #
  84. # # Resolution: 8 bits (0=Unk, 1=Std, 2=Fine,    #
  85. # #                     3=300DPI)                #
  86. # # Encoding: 8 bits (2=MH, 4=MMR, 7=JPEG)       #
  87. # # Reserved1: 32 bits (0)                       #
  88. # # Reserved2: 32 bits (0)                       #
  89. # #----------------------------------------------#
  90. # # Page 1 Header: Total 24 bytes                #
  91. # #..............................................#
  92. # # Page number: 32 bits (1 based)               #
  93. # # Pixels per row: 32 bits                      #
  94. # # Rows this page: 32 bits                      #
  95. # # Image bytes this page(=x): 32 bits           #
  96. # # Thumbnail bytes this page(=y): 32 bits       #
  97. # #  (thumbnail not present if y == 0)           #
  98. # #  (encoding?)                                 #
  99. # #     letter: 134 px wide x 173 px high        #
  100. # #     legal:  134 px wide x 221 px high        #
  101. # #     a4 :    134 px wide x 190 px high        #
  102. # # Reserved3: 32 bits (0)                       #
  103. # #..............................................#
  104. # # Image data: x bytes                          #
  105. # #..............................................#
  106. # # Thumbnail data: y bytes (if present)         #
  107. # #----------------------------------------------#
  108. # # Page 2 Header: Total 24 bytes                #
  109. # #..............................................#
  110. # # Image Data                                   #
  111. # #..............................................#
  112. # # Thumbnail data (if present)                  #
  113. # #----------------------------------------------#
  114. # # ... Pages 3 - (p-1) ...                      #
  115. # #----------------------------------------------#
  116. # # Page p Header: Total 24 bytes                #
  117. # #..............................................#
  118. # # Image Data                                   #
  119. # #..............................................#
  120. # # Thumbnail data (if present)                  #
  121. # #==============================================#
  122. #
  123.  
  124. RESOLUTION_STD = 1
  125. RESOLUTION_FINE = 2
  126. RESOLUTION_300DPI = 3
  127.  
  128. FILE_HEADER_SIZE = 28
  129. PAGE_HEADER_SIZE = 24
  130.  
  131. # **************************************************************************** #
  132.  
  133. ##skip_dn = ["uid=foo,ou=People,dc=example,dc=com",
  134. ##    "uid=bar,ou=People,dc=example,dc=com", "dc=example,dc=com"]
  135.  
  136. class FaxLDIFParser(LDIFParser):
  137.     def __init__(self, input, db):
  138.         LDIFParser.__init__(self, input)
  139.         self.db = db
  140.  
  141.     def handle(self, dn, entry):
  142.         if dn:
  143.             try:
  144.                 firstname = entry['givenName'][0]
  145.             except KeyError:
  146.                 try:
  147.                     firstname = entry['givenname'][0]
  148.                 except KeyError:
  149.                     firstname = ''
  150.  
  151.             try:
  152.                 lastname = entry['sn'][0]
  153.             except KeyError:
  154.                 lastname = ''
  155.  
  156.             try:
  157.                 nickname = entry['cn'][0]
  158.             except KeyError:
  159.                 nickname = firstname + ' ' + lastname
  160.  
  161.             try:
  162.                 fax = entry['facsimiletelephonenumber'][0] # fax
  163.             except KeyError:
  164.                 try:
  165.                     fax = entry['fax'][0]
  166.                 except KeyError:
  167.                     fax  = ''
  168.  
  169.             grps = []
  170.             try:
  171.                 grps = entry['ou']
  172.             except KeyError:
  173.                 pass
  174.  
  175.             grps.append(u'All')
  176.             groups = [g for g in grps if g]
  177.  
  178.             if nickname:
  179.                 log.debug("Import: name=%s, fax=%s, group(s)=%s, notes=%s" % ( nickname, fax, ','.join(groups), dn))
  180.                 self.db.set(nickname, title, firstname, lastname, fax, groups, dn)
  181.  
  182.  
  183.  
  184. # **************************************************************************** #
  185. class FaxAddressBook(object): # Pickle based address book
  186.     def __init__(self):
  187.         self._data = {}
  188.         #
  189.         # { 'name' : {'name': u'',
  190.         #             'firstname' : u'', # NOT USED STARTING IN 2.8.9
  191.         #             'lastname': u', # NOT USED STARTING IN 2.8.9
  192.         #             'title' : u'',  # NOT USED STARTING IN 2.8.9
  193.         #             'fax': u'',
  194.         #             'groups' : [u'', u'', ...],
  195.         #             'notes' : u'', } ...
  196.         # }
  197.         #
  198.         self.load()
  199.  
  200.     def load(self):
  201.         self._fab = os.path.join(prop.user_dir, "fab.pickle")
  202.         #old_fab = os.path.join(prop.user_dir, "fab.db")
  203.  
  204.         # Load the existing pickle if present
  205.         if os.path.exists(self._fab):
  206.             pickle_file = open(self._fab, "r")
  207.             self._data = cPickle.load(pickle_file)
  208.             pickle_file.close()
  209.  
  210.         else:
  211.             self.save() # save the empty file to create the file
  212.  
  213.  
  214.     def set(self, name, title, firstname, lastname, fax, groups, notes):
  215.         try:
  216.             grps = [unicode(s) for s in groups]
  217.         except UnicodeDecodeError:
  218.             grps = [unicode(s.decode('utf-8')) for s in groups]
  219.  
  220.         self._data[unicode(name)] = {'name' : unicode(name),
  221.                                     'title': unicode(title),  # NOT USED STARTING IN 2.8.9
  222.                                     'firstname': unicode(firstname), # NOT USED STARTING IN 2.8.9
  223.                                     'lastname': unicode(lastname), # NOT USED STARTING IN 2.8.9
  224.                                     'fax': unicode(fax),
  225.                                     'notes': unicode(notes),
  226.                                     'groups': grps}
  227.  
  228.         self.save()
  229.  
  230.     insert = set
  231.  
  232.  
  233.     def set_key_value(self, name, key, value):
  234.         self._data[unicode(name)][key] = value
  235.         self.save()
  236.  
  237.  
  238.     def get(self, name):
  239.         return self._data.get(name, None)
  240.  
  241.     select = get
  242.  
  243.     def rename(self, old_name, new_name):
  244.         try:
  245.             self._data[old_name]
  246.         except KeyError:
  247.             return
  248.         else:
  249.             try:
  250.                 self._data[new_name]
  251.             except KeyError:
  252.                 self._data[new_name] = self._data[old_name].copy()
  253.                 del self._data[old_name]
  254.                 self.save()
  255.  
  256.  
  257.     def get_all_groups(self):
  258.         all_groups = []
  259.         for e, v in self._data.items():
  260.             for g in v['groups']:
  261.                 if g not in all_groups:
  262.                     all_groups.append(g)
  263.         return all_groups
  264.  
  265.  
  266.     def get_all_records(self):
  267.         return self._data
  268.  
  269.  
  270.     def get_all_names(self):
  271.         return self._data.keys()
  272.  
  273.  
  274.     def save(self):
  275.         try:
  276.             pickle_file = open(self._fab, "w")
  277.             cPickle.dump(self._data, pickle_file, cPickle.HIGHEST_PROTOCOL)
  278.             pickle_file.close()
  279.         except IOError:
  280.             log.error("I/O error saving fab file.")
  281.  
  282.  
  283.     def clear(self):
  284.         self._data = {}
  285.         self.save()
  286.  
  287.  
  288.     def delete(self, name):
  289.         if name in self._data:
  290.             del self._data[name]
  291.             self.save()
  292.             return True
  293.  
  294.         return False
  295.  
  296.  
  297.     def last_modification_time(self):
  298.         try:
  299.             return os.stat(self._fab).st_mtime
  300.         except OSError:
  301.             return 0
  302.  
  303.  
  304.     def update_groups(self, group, members):
  305.         for e, v in self._data.items():
  306.             if v['name'] in members: # membership indicated
  307.                 if not group in v['groups']:
  308.                     v['groups'].append(unicode(group))
  309.             else:
  310.                 if group in v['groups']:
  311.                     v['groups'].remove(unicode(group))
  312.         self.save()
  313.  
  314.  
  315.     def delete_group(self, group):
  316.         for e, v in self._data.items():
  317.             if group in v['groups']:
  318.                 v['groups'].remove(unicode(group))
  319.         self.save()
  320.  
  321.  
  322.     def group_members(self, group):
  323.         members = []
  324.         for e, v in self._data.items():
  325.             if group in v['groups']:
  326.                 members.append(e)
  327.         return members
  328.  
  329.  
  330.     def add_to_group(self, group, members):
  331.         group_members = self.group_members(group)
  332.         self.update_groups(group, group_members + members)
  333.  
  334.  
  335.     def remove_from_group(self, group, remove_members):
  336.         group_members = self.group_members(group)
  337.         new_group_members = []
  338.         for m in group_members:
  339.             if m not in remove_members:
  340.                 new_group_members.append(m)
  341.  
  342.         self.update_groups(group, new_group_members)
  343.  
  344.  
  345.     def rename_group(self, old_group, new_group):
  346.         members = self.group_members(old_group)
  347.         self.update_groups(old_group, [])
  348.         self.update_groups(new_group, members)
  349.  
  350.  
  351.     def import_ldif(self, filename):
  352.         try:
  353.             data = open(filename, 'r').read()
  354.             log.debug_block(filename, data)
  355.             parser = FaxLDIFParser(open(filename, 'r'), self)
  356.             parser.parse()
  357.             self.save()
  358.             return True, ''
  359.         except ValueError, e:
  360.             return False, e.message
  361.  
  362.  
  363.     def import_vcard(self, filename):
  364.         data = file(filename, 'r').read()
  365.         log.debug_block(filename, data)
  366.  
  367.         for card in vcard.VCards(vcard.VFile(vcard.opentextfile(filename))):
  368.             log.debug(card)
  369.  
  370.             if card['name']:
  371.                 fax = ''
  372.                 for x in range(1, 9999):
  373.                     if x == 1:
  374.                         s = 'phone'
  375.                     else:
  376.                         s = 'phone%d' % x
  377.  
  378.                     try:
  379.                         card[s]
  380.                     except KeyError:
  381.                         break
  382.                     else:
  383.                         if 'fax' in card[s]['type']:
  384.                             fax = card[s]['number']
  385.                             break
  386.  
  387.                 org = card.get('organisation', '')
  388.                 if org:
  389.                     org = [org]
  390.                 else:
  391.                     org = card.get('categories', '').split(';')
  392.                     if not org:
  393.                         org = []
  394.  
  395.                 org.append(u'All')
  396.                 groups = [o for o in org if o]
  397.  
  398.                 name = card['name']
  399.                 notes = card.get('notes', u'')
  400.                 log.debug("Import: name=%s, fax=%s group(s)=%s notes=%s" % (name, fax, ','.join(groups), notes))
  401.                 self.set(name, u'', u'', u'', fax, groups, notes)
  402.  
  403.         return True, ''
  404.  
  405.  
  406. # **************************************************************************** #
  407. class FaxDevice(device.Device):
  408.  
  409.     def __init__(self, device_uri=None, printer_name=None,
  410.                  callback=None,
  411.                  fax_type=FAX_TYPE_NONE,
  412.                  disable_dbus=False):
  413.  
  414.         device.Device.__init__(self, device_uri, printer_name,
  415.                                None, callback, disable_dbus)
  416.  
  417.         self.send_fax_thread = None
  418.         self.upload_log_thread = None
  419.         self.fax_type = fax_type
  420.  
  421.         if not disable_dbus:
  422.             session_bus = dbus.SessionBus()
  423.             self.service = session_bus.get_object('com.hplip.StatusService', "/com/hplip/StatusService")
  424.         else:
  425.             self.service = None
  426.  
  427.  
  428.     def setPhoneNum(self, num):
  429.         raise AttributeError
  430.  
  431.     def getPhoneNum(self):
  432.         raise AttributeError
  433.  
  434.     phone_num = property(getPhoneNum, setPhoneNum)
  435.  
  436.  
  437.     def setStationName(self, name):
  438.         raise AttributeError
  439.  
  440.     def getStationName(self):
  441.         raise AttributeError
  442.  
  443.     station_name = property(getStationName, setStationName)
  444.  
  445.     def setDateAndTime(self):
  446.         raise AttributeError
  447.  
  448.     def uploadLog(self):
  449.         raise AttributeError
  450.  
  451.     def isUploadLogActive(self):
  452.         raise AttributeError
  453.  
  454.     def waitForUploadLogThread(self):
  455.         raise AttributeError
  456.  
  457.     def sendFaxes(self, phone_num_list, fax_file_list, cover_message='', cover_re='',
  458.                   cover_func=None, preserve_formatting=False, printer_name='',
  459.                   update_queue=None, event_queue=None):
  460.  
  461.         raise AttributeError
  462.  
  463.     def isSendFaxActive(self):
  464.         if self.send_fax_thread is not None:
  465.             return self.send_fax_thread.isAlive()
  466.         else:
  467.             return False
  468.  
  469.     def waitForSendFaxThread(self):
  470.         if self.send_fax_thread is not None and \
  471.             self.send_fax_thread.isAlive():
  472.  
  473.             try:
  474.                 self.send_fax_thread.join()
  475.             except KeyboardInterrupt:
  476.                 pass
  477.  
  478.  
  479. # **************************************************************************** #
  480.  
  481.  
  482. def getFaxDevice(device_uri=None, printer_name=None,
  483.                  callback=None,
  484.                  fax_type=FAX_TYPE_NONE,
  485.                  disable_dbus=False):
  486.  
  487.     if fax_type == FAX_TYPE_NONE:
  488.         if device_uri is None and printer_name is not None:
  489.             printers = cups.getPrinters()
  490.  
  491.             for p in printers:
  492.                 if p.name.lower() == printer_name.lower():
  493.                     device_uri = p.device_uri
  494.                     break
  495.             else:
  496.                 raise Error(ERROR_DEVICE_NOT_FOUND)
  497.  
  498.         if device_uri is not None:
  499.             mq = device.queryModelByURI(device_uri)
  500.             fax_type = mq['fax-type']
  501.  
  502.     log.debug("fax-type=%d" % fax_type)
  503.  
  504.     if fax_type in (FAX_TYPE_BLACK_SEND_EARLY_OPEN, FAX_TYPE_BLACK_SEND_LATE_OPEN):
  505.         from pmlfax import PMLFaxDevice
  506.         return PMLFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus)
  507.  
  508.     elif fax_type == FAX_TYPE_SOAP:
  509.         from soapfax import SOAPFaxDevice
  510.         return SOAPFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus)
  511.  
  512.     else:
  513.         raise Error(ERROR_DEVICE_DOES_NOT_SUPPORT_OPERATION)
  514.  
  515. # **************************************************************************** #
  516.  
  517.  
  518. # TODO: Define these in only 1 place!
  519. STATE_DONE = 0
  520. STATE_ABORTED = 10
  521. STATE_SUCCESS = 20
  522. STATE_BUSY = 25
  523. STATE_READ_SENDER_INFO = 30
  524. STATE_PRERENDER = 40
  525. STATE_COUNT_PAGES = 50
  526. STATE_NEXT_RECIPIENT = 60
  527. STATE_COVER_PAGE = 70
  528. STATE_SINGLE_FILE = 80
  529. STATE_MERGE_FILES = 90
  530. STATE_SINGLE_FILE = 100
  531. STATE_SEND_FAX = 110
  532. STATE_CLEANUP = 120
  533. STATE_ERROR = 130
  534.  
  535. class FaxSendThread(threading.Thread):
  536.     def __init__(self, dev, service, phone_num_list, fax_file_list,
  537.                  cover_message='', cover_re='', cover_func=None, preserve_formatting=False,
  538.                  printer_name='', update_queue=None, event_queue=None):
  539.  
  540.         threading.Thread.__init__(self)
  541.  
  542.         self.dev = dev # device.Device
  543.         self.service = service # dbus proxy to status server object
  544.         self.phone_num_list = phone_num_list
  545.         self.fax_file_list = fax_file_list
  546.         self.update_queue = update_queue
  547.         self.event_queue = event_queue
  548.         self.cover_message = cover_message
  549.         self.cover_re = cover_re
  550.         self.cover_func = cover_func
  551.         self.current_printer = printer_name
  552.         self.stream = StringIO()
  553.         self.prev_update = ''
  554.         self.remove_temp_file = False
  555.         self.preserve_formatting = preserve_formatting
  556.         self.results = {} # {'file' : error_code,...}
  557.         self.cover_page_present = False
  558.         self.recipient_file_list = []
  559.         self.f = None # final file of fax data to send (pages merged)
  560.         self.job_hort_dpi = 0
  561.         self.job_hort_dpi = 0
  562.         self.job_vert_dpi = 0
  563.         self.job_page_size = 0
  564.         self.job_resolution = 0
  565.         self.job_encoding = 0
  566.  
  567.  
  568.     def pre_render(self, state):
  569.         # pre-render each page that needs rendering
  570.         # except for the cover page
  571.         self.cover_page_present = False
  572.         log.debug(self.fax_file_list)
  573.  
  574.         for fax_file in self.fax_file_list: # (file, type, desc, title)
  575.             fax_file_name, fax_file_type, fax_file_desc, \
  576.                 fax_file_title, fax_file_pages = fax_file
  577.  
  578.             if fax_file_type == "application/hplip-fax-coverpage": # render later
  579.                 self.cover_page_present = True
  580.                 log.debug("Skipping coverpage")
  581.  
  582.             #if fax_file_type == "application/hplip-fax": # already rendered
  583.             else:
  584.                 self.rendered_file_list.append((fax_file_name, "application/hplip-fax",
  585.                     "HP Fax", fax_file_title))
  586.  
  587.                 log.debug("Processing pre-rendered file: %s (%d pages)" %
  588.                     (fax_file_name, fax_file_pages))
  589.  
  590.             if self.check_for_cancel():
  591.                 state = STATE_ABORTED
  592.  
  593.         log.debug(self.rendered_file_list)
  594.  
  595.         if self.check_for_cancel():
  596.             state = STATE_ABORTED
  597.  
  598.         return state
  599.  
  600.  
  601.     def count_pages(self, state):
  602.         self.recipient_file_list = self.rendered_file_list[:]
  603.         log.debug("Counting total pages...")
  604.         self.job_total_pages = 0
  605.         log.debug(self.recipient_file_list)
  606.  
  607.         i = 0
  608.         for fax_file in self.recipient_file_list: # (file, type, desc, title)
  609.             fax_file_name = fax_file[0]
  610.             log.debug("Processing file (counting pages): %s..." % fax_file_name)
  611.  
  612.             #self.write_queue((STATUS_PROCESSING_FILES, self.job_total_pages, ''))
  613.  
  614.             if os.path.exists(fax_file_name):
  615.                 self.results[fax_file_name] = ERROR_SUCCESS
  616.                 fax_file_fd = file(fax_file_name, 'r')
  617.                 header = fax_file_fd.read(FILE_HEADER_SIZE)
  618.  
  619.                 magic, version, total_pages, hort_dpi, vert_dpi, page_size, \
  620.                     resolution, encoding, reserved1, reserved2 = \
  621.                         self.decode_fax_header(header)
  622.  
  623.                 if magic != 'hplip_g3':
  624.                     log.error("Invalid file header. Bad magic.")
  625.                     self.results[fax_file_name] = ERROR_FAX_INVALID_FAX_FILE
  626.                     state = STATE_ERROR
  627.                     continue
  628.  
  629.                 if not i:
  630.                     self.job_hort_dpi, self.job_vert_dpi, self.job_page_size, \
  631.                         self.job_resolution, self.job_encoding = \
  632.                         hort_dpi, vert_dpi, page_size, resolution, encoding
  633.  
  634.                     i += 1
  635.                 else:
  636.                     if self.job_hort_dpi != hort_dpi or \
  637.                         self.job_vert_dpi != vert_dpi or \
  638.                         self.job_page_size != page_size or \
  639.                         self.job_resolution != resolution or \
  640.                         self.job_encoding != encoding:
  641.  
  642.                         log.error("Incompatible options for file: %s" % fax_file_name)
  643.                         self.results[fax_file_name] = ERROR_FAX_INCOMPATIBLE_OPTIONS
  644.                         state = STATE_ERROR
  645.  
  646.  
  647.                 log.debug("Magic=%s Ver=%d Pages=%d hDPI=%d vDPI=%d Size=%d Res=%d Enc=%d" %
  648.                           (magic, version, total_pages, hort_dpi,
  649.                            vert_dpi, page_size, resolution, encoding))
  650.  
  651.                 self.job_total_pages += total_pages
  652.  
  653.                 fax_file_fd.close()
  654.  
  655.             else:
  656.                 log.error("Unable to find HP Fax file: %s" % fax_file_name)
  657.                 self.results[fax_file_name] = ERROR_FAX_FILE_NOT_FOUND
  658.                 state = STATE_ERROR
  659.                 break
  660.  
  661.             if self.check_for_cancel():
  662.                 state = STATE_ABORTED
  663.                 break
  664.  
  665.  
  666.         if self.cover_page_present:
  667.             self.job_total_pages += 1 # Cover pages are truncated to 1 page
  668.  
  669.         log.debug("Total fax pages=%d" % self.job_total_pages)
  670.  
  671.         return state
  672.  
  673.     def decode_fax_header(self, header):
  674.         try:
  675.             return struct.unpack(">8sBIHHBBBII", header)
  676.         except struct.error:
  677.             return -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
  678.  
  679.     def decode_page_header(self, header):
  680.         try:
  681.             return struct.unpack(">IIIIII", header)
  682.         except struct.error:
  683.             return -1, -1, -1, -1, -1, -1
  684.  
  685.     def cover_page(self,  recipient):
  686.         if self.job_total_pages > 1:
  687.             state = STATE_MERGE_FILES
  688.         else:
  689.             state = STATE_SINGLE_FILE
  690.  
  691.         if self.cover_page_present:
  692.             log.debug("Creating cover page for recipient: %s" % recipient['name'])
  693.             fax_file, canceled = self.render_cover_page(recipient)
  694.  
  695.             if canceled:
  696.                 state = STATE_ABORTED
  697.             elif not fax_file:
  698.                 state = STATE_ERROR # timeout
  699.             else:
  700.                 self.recipient_file_list.insert(0, (fax_file, "application/hplip-fax",
  701.                                                     "HP Fax", 'Cover Page'))
  702.  
  703.                 log.debug("Cover page G3 file: %s" % fax_file)
  704.  
  705.                 self.results[fax_file] = ERROR_SUCCESS
  706.  
  707.         return state
  708.  
  709.     def single_file(self, state):
  710.         state = STATE_SEND_FAX
  711.  
  712.         log.debug("Processing single file...")
  713.         self.f = self.recipient_file_list[0][0]
  714.  
  715.         try:
  716.             f_fd = file(self.f, 'r')
  717.         except IOError:
  718.             log.error("Unable to open fax file: %s" % self.f)
  719.             state = STATE_ERROR
  720.         else:
  721.             header = f_fd.read(FILE_HEADER_SIZE)
  722.  
  723.             magic, version, total_pages, hort_dpi, vert_dpi, page_size, \
  724.                 resolution, encoding, reserved1, reserved2 = self.decode_fax_header(header)
  725.  
  726.             self.results[self.f] = ERROR_SUCCESS
  727.  
  728.             if magic != 'hplip_g3':
  729.                 log.error("Invalid file header. Bad magic.")
  730.                 self.results[self.f] = ERROR_FAX_INVALID_FAX_FILE
  731.                 state = STATE_ERROR
  732.  
  733.             log.debug("Magic=%s Ver=%d Pages=%d hDPI=%d vDPI=%d Size=%d Res=%d Enc=%d" %
  734.                       (magic, version, total_pages, hort_dpi, vert_dpi,
  735.                        page_size, resolution, encoding))
  736.  
  737.             f_fd.close()
  738.  
  739.         return state
  740.  
  741.  
  742.     def merge_files(self, state):
  743.         log.debug("%s State: Merge multiple files" % ("*"*20))
  744.         log.debug(self.recipient_file_list)
  745.         log.debug("Merging g3 files...")
  746.         self.remove_temp_file = True
  747.  
  748.         if self.job_total_pages:
  749.             f_fd, self.f = utils.make_temp_file()
  750.             log.debug("Temp file=%s" % self.f)
  751.  
  752.             data = struct.pack(">8sBIHHBBBII", "hplip_g3", 1L, self.job_total_pages,
  753.                 self.job_hort_dpi, self.job_vert_dpi, self.job_page_size,
  754.                 self.job_resolution, self.job_encoding,
  755.                 0L, 0L)
  756.  
  757.             os.write(f_fd, data)
  758.  
  759.             job_page_num = 1
  760.  
  761.             for fax_file in self.recipient_file_list:
  762.                 fax_file_name = fax_file[0]
  763.                 log.debug("Processing file: %s..." % fax_file_name)
  764.  
  765.                 if self.results[fax_file_name] == ERROR_SUCCESS:
  766.                     fax_file_fd = file(fax_file_name, 'r')
  767.                     header = fax_file_fd.read(FILE_HEADER_SIZE)
  768.  
  769.                     magic, version, total_pages, hort_dpi, vert_dpi, page_size, \
  770.                         resolution, encoding, reserved1, reserved2 = self.decode_fax_header(header)
  771.  
  772.                     if magic != 'hplip_g3':
  773.                         log.error("Invalid file header. Bad magic.")
  774.                         state = STATE_ERROR
  775.                         break
  776.  
  777.                     log.debug("Magic=%s Ver=%d Pages=%d hDPI=%d vDPI=%d Size=%d Res=%d Enc=%d" %
  778.                               (magic, version, total_pages, hort_dpi, vert_dpi, page_size, resolution, encoding))
  779.  
  780.                     for p in range(total_pages):
  781.                         header = fax_file_fd.read(PAGE_HEADER_SIZE)
  782.  
  783.                         page_num, ppr, rpp, bytes_to_read, thumbnail_bytes, reserved2 = \
  784.                             self.decode_page_header(header)
  785.  
  786.                         if page_num == -1:
  787.                             log.error("Page header error")
  788.                             state - STATE_ERROR
  789.                             break
  790.  
  791.                         header = struct.pack(">IIIIII", job_page_num, ppr, rpp, bytes_to_read, thumbnail_bytes, 0L)
  792.                         os.write(f_fd, header)
  793.  
  794.                         self.write_queue((STATUS_PROCESSING_FILES, job_page_num, ''))
  795.  
  796.                         log.debug("Page=%d PPR=%d RPP=%d BPP=%d Thumb=%s" %
  797.                                   (page_num, ppr, rpp, bytes_to_read, thumbnail_bytes))
  798.  
  799.                         os.write(f_fd, fax_file_fd.read(bytes_to_read))
  800.                         job_page_num += 1
  801.  
  802.                     fax_file_fd.close()
  803.  
  804.                     if self.check_for_cancel():
  805.                         state = STATE_ABORTED
  806.                         break
  807.  
  808.                 else:
  809.                     log.error("Skipping file: %s" % fax_file_name)
  810.                     continue
  811.  
  812.             os.close(f_fd)
  813.             log.debug("Total pages=%d" % self.job_total_pages)
  814.  
  815.         return state
  816.  
  817.  
  818.     def next_recipient_gen(self):
  819.         for a in self.phone_num_list:
  820.             yield a
  821.  
  822.  
  823.     def render_file(self, path, title, mime_type, force_single_page=False):
  824.         all_pages = True
  825.         page_range = ''
  826.         page_set = 0
  827.         nup = 1
  828.  
  829.         cups.resetOptions()
  830.  
  831.         if mime_type in ["application/x-cshell",
  832.                          "application/x-perl",
  833.                          "application/x-python",
  834.                          "application/x-shell",
  835.                          "text/plain",]:
  836.  
  837.             cups.addOption('prettyprint')
  838.  
  839.         if nup > 1:
  840.             cups.addOption('number-up=%d' % nup)
  841.  
  842.         if force_single_page:
  843.             cups.addOption('page-ranges=1') # Force coverpage to 1 page
  844.  
  845.         sent_job_id = cups.printFile(self.current_printer, path, title)
  846.         cups.resetOptions()
  847.  
  848.         log.debug("Job ID=%d" % sent_job_id)
  849.         job_id = 0
  850.  
  851.         time.sleep(1)
  852.  
  853.         fax_file = ''
  854.         complete = False
  855.  
  856.         end_time = time.time() + 300.0 # wait for 5 min. max
  857.         while time.time() < end_time:
  858.             log.debug("Waiting for fax...")
  859.  
  860.             result = list(self.service.CheckForWaitingFax(self.dev.device_uri, prop.username, sent_job_id))
  861.  
  862.             fax_file = str(result[7])
  863.             log.debug("Fax file=%s" % fax_file)
  864.  
  865.             if fax_file:
  866.                 break
  867.  
  868.             if self.check_for_cancel():
  869.                 log.error("Render canceled. Canceling job #%d..." % sent_job_id)
  870.                 cups.cancelJob(sent_job_id)
  871.                 return '', True
  872.  
  873.             time.sleep(1)
  874.  
  875.         else:
  876.             log.error("Timeout waiting for rendering. Canceling job #%d..." % sent_job_id)
  877.             cups.cancelJob(sent_job_id)
  878.             return '', False
  879.  
  880.         return fax_file, False
  881.  
  882.  
  883.     def check_for_cancel(self):
  884.         canceled = False
  885.         while self.event_queue.qsize():
  886.             try:
  887.                 event = self.event_queue.get(0)
  888.                 if event[0] == EVENT_FAX_SEND_CANCELED:
  889.                     canceled = True
  890.                     log.debug("Cancel pressed!")
  891.             except Queue.Empty:
  892.                 break
  893.  
  894.         return canceled
  895.  
  896.     def render_cover_page(self, a):
  897.         log.debug("Creating cover page...")
  898.  
  899.         pdf = self.cover_func(page_size=coverpages.PAGE_SIZE_LETTER,
  900.                               total_pages=self.job_total_pages,
  901.  
  902.                               recipient_name=a['name'],
  903.                               recipient_phone='', # ???
  904.                               recipient_fax=a['fax'],
  905.  
  906.                               sender_name=self.sender_name,
  907.                               sender_phone=user_conf.get('fax', 'voice_phone'),
  908.                               sender_fax=self.sender_fax,
  909.                               sender_email=user_conf.get('fax', 'email_address'),
  910.  
  911.                               regarding=self.cover_re,
  912.                               message=self.cover_message,
  913.                               preserve_formatting=self.preserve_formatting)
  914.  
  915.         log.debug("PDF File=%s" % pdf)
  916.         fax_file, canceled = self.render_file(pdf, 'Cover Page', "application/pdf",
  917.             force_single_page=True)
  918.  
  919.         try:
  920.             os.remove(pdf)
  921.         except IOError:
  922.             pass
  923.  
  924.         return fax_file, canceled
  925.  
  926.  
  927.     def write_queue(self, message):
  928.         if self.update_queue is not None and message != self.prev_update:
  929.             self.update_queue.put(message)
  930.             time.sleep(0)
  931.             self.prev_update = message
  932.  
  933.  
  934.     def run(self):
  935.         pass
  936.  
  937.  
  938.  
  939.